/*
 * FileSystem.cpp
 *
 *  Created on: 10 jul. 2012
 *      Author: niels
 */

#include "FileSystem.h"
#include "swap.h"

void FileSystem::printSuperBlock()
{
    cout << "number of blocks in i-list is: " << filesystem.s_isize << endl;
    cout << "number of blocks on volume is: " << filesystem.s_fsize << endl;
    cout << "number of freeblocks in free[] is: " << filesystem.s_nfree << endl;
    cout << "blocks: ";
    for(int i = 0; i < filesystem.s_nfree; i++){
		cout << filesystem.s_free[i] << ", " ;
	}
    cout << "\n number of free inodes: " << filesystem.s_ninode << endl;
    cout << "inode: ";
    for(int i = 0;i < filesystem.s_ninode;i++){
        cout << filesystem.s_inode[i] << ", ";
    }
    string s = getchars(&filesystem.s_flock, 0, 2);
    cout << endl << "freelist lock flag is " << filesystem.s_flock << endl;
    cout << "Last update was ";
    printf("%s", ctime(&filesystem.s_time));
    cout  << "total number of free blocks is: " << filesystem.s_tfree << endl;
    cout << "total number of free inodes is: " << filesystem.s_tinode << endl;
    cout << "interleave factors are: " << filesystem.s_m << " and " << filesystem.s_n  << endl;
    cout << "File system name is: " << filesystem.s_fname << endl;
    cout << "File system pack is: " << filesystem.s_fpack << endl;
}

FileSystem::FileSystem(const char * device ) {
    disk = new fstream();
    disk->open(device, ios::in | ios::out | ios::binary);
	if(disk->is_open()) cout << "file open" << endl;
	else cout << "file not found" << endl;
    disk->seekg(0);

    block* superBlock = readBlock(SUPERB);

    int offset = 0;
    superBlock->readInto(filesystem.s_isize, offset);
    superBlock->readInto(filesystem.s_fsize, offset);
    superBlock->readInto(filesystem.s_nfree, offset);
    superBlock->readIntoArray(filesystem.s_free, NICFREE, offset);
    superBlock->readInto(filesystem.s_ninode, offset);
    superBlock->readIntoArray(filesystem.s_inode, NICINOD, offset);
    superBlock->readInto(filesystem.s_flock, offset);
    superBlock->readInto(filesystem.s_ilock, offset);
    superBlock->readInto(filesystem.s_fmod, offset);
    superBlock->readInto(filesystem.s_ronly, offset);
    superBlock->readInto(filesystem.s_time, offset);
    superBlock->readInto(filesystem.s_tfree, offset);
    superBlock->readInto(filesystem.s_tinode, offset);
    superBlock->readInto(filesystem.s_m, offset);
    superBlock->readInto(filesystem.s_n, offset);
    superBlock->readIntoArray(filesystem.s_fname, 6, offset);
    superBlock->readIntoArray(filesystem.s_fpack, 6, offset);
    printSuperBlock();
    return;
}

FileSystem::~FileSystem() {
	// TODO Auto-generated destructor stub
}

block* FileSystem::readBlock(daddr_x address)
{
	block* b = new block;
	disk->seekg(address * DBLKSIZ );
	disk->read((char*)b->bytes, DBLKSIZ);
	b->address = address;
	return b;
}


void FileSystem::freeBlock(daddr_x address)
{
	block* block = readBlock(address);
	if(filesystem.s_nfree == NICFREE)//list is full
	{
		//build new freelist
		fblk fblock = getFblk(block);
		memcpy( fblock.df_free, filesystem.s_free, 200);
//		fblock.df_free = filesystem.s_free;
		fblock.df_nfree = filesystem.s_nfree;

		writeToBlock(fblock, block);
		writeBlock(block);
		filesystem.s_nfree = 0;

	}
	filesystem.s_free[filesystem.s_nfree] = address;
	filesystem.s_nfree++;
	filesystem.s_tfree++;


	//TODO write block and superblock to disk
	delete block;
}

daddr_x FileSystem::getFreeBlock()

{
	daddr_x nextaddr = filesystem.s_free[filesystem.s_nfree-1];
	if(nextaddr == 0)//no more free blocks available
	{
		cout << "NO MORE FREE BLOCKS!! (or big bad fail)" << endl;
		return 0;
	}
	else
	{
		block* block = readBlock(nextaddr);
		if(filesystem.s_nfree == 1 && nextaddr != 0)
		{
			fblk fblock = getFblk(block);
			memcpy(filesystem.s_free , fblock.df_free, sizeof(fblock.df_free));
//			filesystem.s_free = fblock.df_free;
			filesystem.s_nfree = fblock.df_nfree;
			filesystem.s_tfree--;
		}
		else
		{
			filesystem.s_nfree--;
			filesystem.s_tfree--;
		}
		daddr_x address  = block->address;
		//TODO write block and superblock to disk
		delete block;
		return address;
	}


}

dinode FileSystem::getInode(ino_x number)
{

	daddr_x blocknumber = itod(number);
	block* block = readBlock(blocknumber);
	int offset = itoo(number);
	cout << "offset " << offset << endl;


	int byteOffset = offset * INSIZ;

	dinode inode;

	inode.di_mode = getshort(block->bytes, byteOffset); //2Bytes
	byteOffset += 2; // shift offset by 2 Bytes
	inode.di_nlink = getshort(block->bytes, byteOffset); //2Bytes
	byteOffset += 2; // shift offset by 2 Bytes
	inode.di_uid = getshort(block->bytes, byteOffset); //2Bytes
	byteOffset += 2; // shift offset by 2 Bytes
	inode.di_gid = getshort(block->bytes, byteOffset); //2Bytes
	byteOffset += 2; // shift offset by 2 Bytes
	inode.di_size = getlong(block->bytes, byteOffset); //4Bytes
	byteOffset += 4; // shift offset by 4 Bytes
	memcpy(inode.di_addr, block->bytes+byteOffset, 40);
	byteOffset += 40; // shift offset by 40 Bytes
	inode.di_atime = getlong(block->bytes, byteOffset);
	byteOffset += 4; // shift offset by 4 Bytes
	inode.di_mtime = getlong(block->bytes, byteOffset);
	byteOffset += 4; // shift offset by 4 Bytes
	inode.di_ctime = getlong(block->bytes, byteOffset);
	byteOffset += 4; // shift offset by 4 Bytes

	delete block;
	return inode;
}

long FileSystem::getFreeSpace()
{
	int freeblocks = filesystem.s_nfree;
	daddr_x nextaddr = filesystem.s_free[0];
	while(nextaddr != 0)
	{
		block* b = readBlock(nextaddr);
		fblk fblock = getFblk(b);
		nextaddr = fblock.df_free[0];
		freeblocks += fblock.df_nfree;
	}
	cout << "free blocks: "<< freeblocks << endl;
	return freeblocks * DBLKSIZ;
}

fblk FileSystem::getFblk(block* b)
{
        int offset = 4;
        fblk fblock;
        fblock.df_nfree = getlong(b->bytes, 0);
        b->readIntoArray(fblock.df_free, NICFREE, offset);
        return fblock;
}

void FileSystem::writeToBlock(fblk fblock, block* b)
{
	int head = swap_endian(fblock.df_nfree);
	memcpy(b, &head, 4);
	unsigned long value;
	int offset;
	for(int i = 0; i < NICFREE; i++)
	{
		value = swap_endian(fblock.df_free[i]);
		offset = (i+1)*4;
		memcpy(b->bytes+offset, &value, 4);
	}
}




int FileSystem::getFreeInode()
{
	if(filesystem.s_ninode > 0)
	{
		filesystem.s_ninode--;
		filesystem.s_tinode--;
		return filesystem.s_inode[filesystem.s_ninode];
	}
	else if (filesystem.s_tinode > 0)
	{
		short totalInodes = filesystem.s_isize * INOPB;//8 inodes per block
		for(short i = 1; i <= totalInodes; i++)
		{
			dinode inode = getInode(i);
			if(inode.di_mode == 0)
			{
				filesystem.s_inode[filesystem.s_ninode] = i;
				filesystem.s_ninode++;
				if(filesystem.s_ninode == NICINOD)
				{
					return getFreeInode();
				}
			}
		}
		return getFreeInode();
	}
	else return 0;
}

void FileSystem::writeBlock(block* b){
        long position = b->address*DBLKSIZ;
        disk->seekp(position);
        disk->write((char*)b->bytes, DBLKSIZ);
}

void FileSystem::freeInodeData(dinode inode)
{
        for(int i = 0; i < 10; i++)
        {
                daddr_x address = getlong3(inode.di_addr, i*3); //3 Byte longs
                if(address != 0 )freeBlock(address);
        }
        for(int i = 10; i < 13; i++)
        {
                daddr_x address = getlong3(inode.di_addr, i*3); //3 Byte longs
                if(address != 0){
                        freeIndirections(address, i-9);
                        freeBlock(address);
                }
        }
}
void FileSystem::freeIndirections(daddr_x address, int depth)
{
	block* b = readBlock(address);
	depth --;
	for(int i = 0; i < 128; i++){
		daddr_x nxtAddr = getlong(b->bytes, i*4);
		if(nxtAddr != 0)
		{
			if( depth > 0 )freeIndirections(nxtAddr, depth);
			freeBlock(nxtAddr);
		}
	}
}
void FileSystem::freeInode(ino_x number)
{

	dinode node = getInode(number);
	node.di_mode = 0;
	//TODO write to disk
	freeInodeData(node);
	filesystem.s_tinode++;
	if(filesystem.s_ninode < NICINOD)
	{
		filesystem.s_inode[filesystem.s_ninode] = number;
		filesystem.s_ninode++;
	}
}


